.global main
.intel_syntax noprefix
main:
  # 函数调用的第一个参数放在 rdi 中
  mov rdi, rax
  # 1. 将 rip 压栈 (即 leave 的地址)
  # 2. 跳转到 copy 执行
  call copy
  # 当 copy 执行 ret 指令时会跳转到这里执行
  leave
  ret

copy:
  # 这个部分叫做 "function prologue", 几乎每个函数开头都有类似的压栈代码.
  # 这并不是 x86-64 必须的, 但是一种 convention, 编译器会自动添加.
  # 1. 保存 rbp, 即 caller frame 的起始位置.
  push rbp
  # 2. 将当前函数 frame 起始位置赋给 rbp.
  mov rbp, rsp
  # 3. 在栈中开辟空间, 用于存放局部变量
  # 在本例中, 需要8字节用于字符串指针(?), 16字节用于缓冲区.
  # 由于栈需要16字节对齐, 这里共需开辟32字节空间
  subq    $32, %rsp

  # 此时 copy frame 形状如下 (每行8字节):
  #
  #     .-------0x00-------.
  #     :                  :
  #     |                  | <- %rsp
  #     |     padding      |
  #     |       str        |
  #     |  buffer[ 0-7  ]  |
  #     |  buffer[ 8-15 ]  | <- %rbp
  #     | [ main's  %rbp ] |
  #     | [return address] |
  #     |------------------|
  #     :   main's frame   :
  #     `------------------`
  #
  # 或横过来看:
  #
  #   内存低地址                                           内存高地址
  #          str          buffer         sbp       ret
  #       [        ][ 0           15 ][        ][        ] main...
  #   栈顶                                                 栈底

  # x86-64 calling convention 规定函数第一个参数在 rdi 中
  mov -24[rbp], rdi

  # 现在准备好调用 strcpy, 需要两个参数, target 和 destination
  # 我们需要分别将其放在 rdi 和 rsi 中
  # 1. 将 &buffer[0] 作为 target 放入 rdi
  # (lea 会将地址计算出来)
  lea rdi, -16[rbp]

  # 2. 将字符串指针作为 dest 放入 rsi
  # 这里可以不借助栈来传递字符串指针而是直接将 rdi 传给 rsi
  mov rsi, -24[rbp]
  call    strcpy

  # strcpy 返回后, 从这里开始继续执行, 为了打印字符串, 将
  # &buffer[0] 作为参数传递给 puts 函数.
  lea rdi, -16[rbp]
  call    puts

  # 最终, 在返回前需要恢复 rsp 和 rbp
  # 关于 leave 指令可见 https://www.felixcloutier.com/x86/leave 
  leave
  # ret 将返回地址 pop并跳转
  ret
